package com.fasterxml.jackson.core.json;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.core.io.CharTypes;
import com.fasterxml.jackson.core.io.CharacterEscapes;
import com.fasterxml.jackson.core.io.IOContext;
import com.fasterxml.jackson.core.io.NumberOutput;

/**
 * 添加了<code>public void writeEnum(Enum<?> enumValue) throws IOException </code>方法
 */
public class UTF8JsonGenerator extends JsonGeneratorImpl {
    private final static byte BYTE_u = (byte) 'u';
    private final static byte BYTE_0 = (byte) '0';
    private final static byte BYTE_LBRACKET = (byte) '[';
    private final static byte BYTE_RBRACKET = (byte) ']';
    private final static byte BYTE_LCURLY = (byte) '{';
    private final static byte BYTE_RCURLY = (byte) '}';
    private final static byte BYTE_BACKSLASH = (byte) '\\';
    private final static byte BYTE_COMMA = (byte) ',';
    private final static byte BYTE_COLON = (byte) ':';
    private final static int MAX_BYTES_TO_BUFFER = 512;
    private final static byte[] HEX_BYTES_UPPER = CharTypes.copyHexBytes(true);
    private final static byte[] HEX_BYTES_LOWER = CharTypes.copyHexBytes(false);
    private final static byte[] NULL_BYTES = {'n', 'u', 'l', 'l'};
    private final static byte[] TRUE_BYTES = {'t', 'r', 'u', 'e'};
    private final static byte[] FALSE_BYTES = {'f', 'a', 'l', 's', 'e'};
    final protected OutputStream _outputStream;
    protected byte _quoteChar;
    protected byte[] _outputBuffer;
    protected int _outputTail;
    protected final int _outputEnd;
    protected final int _outputMaxContiguous;
    protected char[] _charBuffer;
    protected final int _charBufferLength;
    protected byte[] _entityBuffer;
    protected boolean _bufferRecyclable;

    @SuppressWarnings("deprecation")
    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar) {
        super(ctxt, features, codec);
        _outputStream = out;
        _quoteChar = (byte) quoteChar;

        boolean escapeSlash = isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES.mappedFeature());
        if (quoteChar != '"' || escapeSlash) {
            _outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar, escapeSlash);
        }
        _bufferRecyclable = true;
        _outputBuffer = ctxt.allocWriteEncodingBuffer();
        _outputEnd = _outputBuffer.length;

        _outputMaxContiguous = _outputEnd >> 3;
        _charBuffer = ctxt.allocConcatBuffer();
        _charBufferLength = _charBuffer.length;

        if (isEnabled(Feature.ESCAPE_NON_ASCII)) {
            setHighestNonEscapedChar(127);
        }
    }

    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, char quoteChar, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
        super(ctxt, features, codec);
        _outputStream = out;
        _quoteChar = (byte) quoteChar;
        boolean escapeSlash = isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES.mappedFeature());
        if (quoteChar != '"' || escapeSlash) {
            _outputEscapes = CharTypes.get7BitOutputEscapes(quoteChar, escapeSlash);
        }

        _bufferRecyclable = bufferRecyclable;
        _outputTail = outputOffset;
        _outputBuffer = outputBuffer;
        _outputEnd = _outputBuffer.length;

        _outputMaxContiguous = (_outputEnd >> 3);
        _charBuffer = ctxt.allocConcatBuffer();
        _charBufferLength = _charBuffer.length;
    }

    @Deprecated
    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out) {
        this(ctxt, features, codec, out, JsonFactory.DEFAULT_QUOTE_CHAR);
    }

    @Deprecated
    public UTF8JsonGenerator(IOContext ctxt, int features, ObjectCodec codec, OutputStream out, byte[] outputBuffer, int outputOffset, boolean bufferRecyclable) {
        this(ctxt, features, codec, out, JsonFactory.DEFAULT_QUOTE_CHAR, outputBuffer, outputOffset, bufferRecyclable);
    }

    @Override
    public Object getOutputTarget() {
        return _outputStream;
    }

    @Override
    public int getOutputBuffered() {

        return _outputTail;
    }

    @Override
    public void writeFieldName(String name) throws IOException {
        if (_cfgPrettyPrinter != null) {
            _writePPFieldName(name);
            return;
        }
        final int status = _writeContext.writeFieldName(name);
        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
            _reportError("Can not write a field name, expecting a value");
        }
        if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_COMMA;
        }

        if (_cfgUnqNames) {
            _writeStringSegments(name, false);
            return;
        }
        final int len = name.length();

        if (len > _charBufferLength) {
            _writeStringSegments(name, true);
            return;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;

        if (len <= _outputMaxContiguous) {
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(name, 0, len);
        } else {
            _writeStringSegments(name, 0, len);
        }

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeFieldName(SerializableString name) throws IOException {
        if (_cfgPrettyPrinter != null) {
            _writePPFieldName(name);
            return;
        }
        final int status = _writeContext.writeFieldName(name.getValue());
        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
            _reportError("Can not write a field name, expecting a value");
        }
        if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_COMMA;
        }
        if (_cfgUnqNames) {
            _writeUnq(name);
            return;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(name.asQuotedUTF8());
        } else {
            _outputTail += len;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    private void _writeUnq(SerializableString name) throws IOException {
        int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(name.asQuotedUTF8());
        } else {
            _outputTail += len;
        }
    }

    @Override
    public final void writeStartArray() throws IOException {
        _verifyValueWrite("start an array");
        _writeContext = _writeContext.createChildArrayContext();
        streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeStartArray(this);
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_LBRACKET;
        }
    }

    @Override
    public final void writeStartArray(Object currentValue) throws IOException {
        _verifyValueWrite("start an array");
        _writeContext = _writeContext.createChildArrayContext(currentValue);
        streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeStartArray(this);
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_LBRACKET;
        }
    }

    @Override
    public void writeStartArray(Object currentValue, int size) throws IOException {
        _verifyValueWrite("start an array");
        _writeContext = _writeContext.createChildArrayContext(currentValue);
        streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeStartArray(this);
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_LBRACKET;
        }
    }

    @Override
    public final void writeEndArray() throws IOException {
        if (!_writeContext.inArray()) {
            _reportError("Current context not Array but " + _writeContext.typeDesc());
        }
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeEndArray(this, _writeContext.getEntryCount());
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_RBRACKET;
        }
        _writeContext = _writeContext.clearAndGetParent();
    }

    @Override
    public final void writeStartObject() throws IOException {
        _verifyValueWrite("start an object");
        _writeContext = _writeContext.createChildObjectContext();
        streamWriteConstraints().validateNestingDepth(_writeContext.getNestingDepth());
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeStartObject(this);
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_LCURLY;
        }
    }

    @Override
    public void writeStartObject(Object forValue) throws IOException {
        _verifyValueWrite("start an object");
        JsonWriteContext ctxt = _writeContext.createChildObjectContext(forValue);
        streamWriteConstraints().validateNestingDepth(ctxt.getNestingDepth());
        _writeContext = ctxt;
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeStartObject(this);
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = '{';
        }
    }

    @Override
    public void writeStartObject(Object forValue, int size) throws IOException {
        writeStartObject(forValue);
    }

    @Override
    public final void writeEndObject() throws IOException {
        if (!_writeContext.inObject()) {
            _reportError("Current context not Object but " + _writeContext.typeDesc());
        }
        if (_cfgPrettyPrinter != null) {
            _cfgPrettyPrinter.writeEndObject(this, _writeContext.getEntryCount());
        } else {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = BYTE_RCURLY;
        }
        _writeContext = _writeContext.clearAndGetParent();
    }

    protected final void _writePPFieldName(String name) throws IOException {
        int status = _writeContext.writeFieldName(name);
        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
            _reportError("Can not write a field name, expecting a value");
        }
        if ((status == JsonWriteContext.STATUS_OK_AFTER_COMMA)) {
            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
        } else {
            _cfgPrettyPrinter.beforeObjectEntries(this);
        }
        if (_cfgUnqNames) {
            _writeStringSegments(name, false);
            return;
        }
        final int len = name.length();
        if (len > _charBufferLength) {
            _writeStringSegments(name, true);
            return;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        name.getChars(0, len, _charBuffer, 0);

        if (len <= _outputMaxContiguous) {
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(_charBuffer, 0, len);
        } else {
            _writeStringSegments(_charBuffer, 0, len);
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    protected final void _writePPFieldName(SerializableString name) throws IOException {
        final int status = _writeContext.writeFieldName(name.getValue());
        if (status == JsonWriteContext.STATUS_EXPECT_VALUE) {
            _reportError("Can not write a field name, expecting a value");
        }
        if (status == JsonWriteContext.STATUS_OK_AFTER_COMMA) {
            _cfgPrettyPrinter.writeObjectEntrySeparator(this);
        } else {
            _cfgPrettyPrinter.beforeObjectEntries(this);
        }

        final boolean addQuotes = !_cfgUnqNames;
        if (addQuotes) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = _quoteChar;
        }
        int len = name.appendQuotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(name.asQuotedUTF8());
        } else {
            _outputTail += len;
        }
        if (addQuotes) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = _quoteChar;
        }
    }

    @Override
    public void writeString(String text) throws IOException {
        _verifyValueWrite(WRITE_STRING);
        if (text == null) {
            _writeNull();
            return;
        }

        final int len = text.length();
        if (len > _outputMaxContiguous) {
            _writeStringSegments(text, true);
            return;
        }
        if ((_outputTail + len) >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _writeStringSegment(text, 0, len);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeString(Reader reader, int len) throws IOException {
        _verifyValueWrite(WRITE_STRING);
        if (reader == null) {
            _reportError("null reader");
            return;
        }

        int toRead = (len >= 0) ? len : Integer.MAX_VALUE;
        final char[] buf = _charBuffer;

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;

        while (toRead > 0) {
            int toReadNow = Math.min(toRead, buf.length);
            int numRead = reader.read(buf, 0, toReadNow);
            if (numRead <= 0) {
                break;
            }
            if ((_outputTail + len) >= _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegments(buf, 0, numRead);

            toRead -= numRead;
        }

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;

        if (toRead > 0 && len >= 0) {
            _reportError("Didn't read enough from reader");
        }
    }

    @Override
    public void writeString(char[] text, int offset, int len) throws IOException {
        _verifyValueWrite(WRITE_STRING);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;

        if (len <= _outputMaxContiguous) {
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(text, offset, len);
        } else {
            _writeStringSegments(text, offset, len);
        }

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public final void writeString(SerializableString text) throws IOException {
        _verifyValueWrite(WRITE_STRING);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        int len = text.appendQuotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(text.asQuotedUTF8());
        } else {
            _outputTail += len;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeRawUTF8String(byte[] text, int offset, int len) throws IOException {
        _checkRangeBoundsForByteArray(text, offset, len);
        _verifyValueWrite(WRITE_STRING);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _writeBytes(text, offset, len);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeUTF8String(byte[] text, int offset, int len) throws IOException {
        _checkRangeBoundsForByteArray(text, offset, len);
        _verifyValueWrite(WRITE_STRING);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;

        if (len <= _outputMaxContiguous) {
            _writeUTF8Segment(text, offset, len);
        } else {
            _writeUTF8Segments(text, offset, len);
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeRaw(String text) throws IOException {
        final int len = text.length();
        final char[] buf = _charBuffer;
        if (len <= buf.length) {
            text.getChars(0, len, buf, 0);
            writeRaw(buf, 0, len);
        } else {
            writeRaw(text, 0, len);
        }
    }

    @Override
    public void writeRaw(String text, int offset, int len) throws IOException {
        _checkRangeBoundsForString(text, offset, len);

        final char[] buf = _charBuffer;
        final int cbufLen = buf.length;

        if (len <= cbufLen) {
            text.getChars(offset, offset + len, buf, 0);
            writeRaw(buf, 0, len);
            return;
        }

        final int maxChunk = Math.min(cbufLen,
                (_outputEnd >> 2) + (_outputEnd >> 4));
        final int maxBytes = maxChunk * 3;

        while (len > 0) {
            int len2 = Math.min(maxChunk, len);
            text.getChars(offset, offset + len2, buf, 0);
            if ((_outputTail + maxBytes) > _outputEnd) {
                _flushBuffer();
            }

            if (len2 > 1) {
                char ch = buf[len2 - 1];
                if ((ch >= SURR1_FIRST) && (ch <= SURR1_LAST)) {
                    --len2;
                }
            }
            _writeRawSegment(buf, 0, len2);
            offset += len2;
            len -= len2;
        }
    }

    @Override
    public void writeRaw(SerializableString text) throws IOException {
        int len = text.appendUnquotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(text.asUnquotedUTF8());
        } else {
            _outputTail += len;
        }
    }

    @Override
    public void writeRawValue(SerializableString text) throws IOException {
        _verifyValueWrite(WRITE_RAW);
        int len = text.appendUnquotedUTF8(_outputBuffer, _outputTail);
        if (len < 0) {
            _writeBytes(text.asUnquotedUTF8());
        } else {
            _outputTail += len;
        }
    }

    @Override
    public final void writeRaw(char[] cbuf, int offset, int len) throws IOException {
        _checkRangeBoundsForCharArray(cbuf, offset, len);
        {
            int len3 = len + len + len;
            if ((_outputTail + len3) > _outputEnd) {

                if (_outputEnd < len3) {
                    _writeSegmentedRaw(cbuf, offset, len);
                    return;
                }

                _flushBuffer();
            }
        }
        len += offset;

        main_loop:
        while (offset < len) {
            inner_loop:
            while (true) {
                int ch = cbuf[offset];
                if (ch > 0x7F) {
                    break inner_loop;
                }
                _outputBuffer[_outputTail++] = (byte) ch;
                if (++offset >= len) {
                    break main_loop;
                }
            }
            char ch = cbuf[offset++];
            if (ch < 0x800) {
                _outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6));
                _outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                offset = _outputRawMultiByteChar(ch, cbuf, offset, len);
            }
        }
    }

    @Override
    public void writeRaw(char ch) throws IOException {
        if ((_outputTail + 3) >= _outputEnd) {
            _flushBuffer();
        }
        final byte[] bbuf = _outputBuffer;
        if (ch <= 0x7F) {
            bbuf[_outputTail++] = (byte) ch;
        } else if (ch < 0x800) {
            bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
            bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
        } else {
            _outputRawMultiByteChar(ch, null, 0, 0);
        }
    }

    private void _writeSegmentedRaw(char[] cbuf, int offset, int len) throws IOException {
        final int end = _outputEnd;
        final byte[] bbuf = _outputBuffer;
        final int inputEnd = offset + len;

        main_loop:
        while (offset < inputEnd) {
            inner_loop:
            while (true) {
                int ch = cbuf[offset];
                if (ch > 0x7F) {
                    break inner_loop;
                }
                if (_outputTail >= end) {
                    _flushBuffer();
                }
                bbuf[_outputTail++] = (byte) ch;
                if (++offset >= inputEnd) {
                    break main_loop;
                }
            }
            if ((_outputTail + 3) >= _outputEnd) {
                _flushBuffer();
            }
            char ch = cbuf[offset++];
            if (ch < 0x800) {
                bbuf[_outputTail++] = (byte) (0xc0 | (ch >> 6));
                bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                offset = _outputRawMultiByteChar(ch, cbuf, offset, inputEnd);
            }
        }
    }

    private void _writeRawSegment(char[] cbuf, int offset, int end) throws IOException {
        main_loop:
        while (offset < end) {
            inner_loop:
            while (true) {
                int ch = cbuf[offset];
                if (ch > 0x7F) {
                    break inner_loop;
                }
                _outputBuffer[_outputTail++] = (byte) ch;
                if (++offset >= end) {
                    break main_loop;
                }
            }
            char ch = cbuf[offset++];
            if (ch < 0x800) {
                _outputBuffer[_outputTail++] = (byte) (0xc0 | (ch >> 6));
                _outputBuffer[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                offset = _outputRawMultiByteChar(ch, cbuf, offset, end);
            }
        }
    }

    @Override
    public void writeBinary(Base64Variant b64variant, byte[] data, int offset, int len) throws IOException {
        _checkRangeBoundsForByteArray(data, offset, len);
        _verifyValueWrite(WRITE_BINARY);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _writeBinary(b64variant, data, offset, offset + len);

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public int writeBinary(Base64Variant b64variant, InputStream data, int dataLength) throws IOException {
        _verifyValueWrite(WRITE_BINARY);

        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        byte[] encodingBuffer = _ioContext.allocBase64Buffer();
        int bytes;
        try {
            if (dataLength < 0) {
                bytes = _writeBinary(b64variant, data, encodingBuffer);
            } else {
                int missing = _writeBinary(b64variant, data, encodingBuffer, dataLength);
                if (missing > 0) {
                    _reportError("Too few bytes available: missing " + missing + " bytes (out of " + dataLength + ")");
                }
                bytes = dataLength;
            }
        } finally {
            _ioContext.releaseBase64Buffer(encodingBuffer);
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        return bytes;
    }

    @Override
    public void writeNumber(short s) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);

        if ((_outputTail + 6) >= _outputEnd) {
            _flushBuffer();
        }
        if (_cfgNumbersAsStrings) {
            _writeQuotedShort(s);
            return;
        }
        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
    }

    private void _writeQuotedShort(short s) throws IOException {
        if ((_outputTail + 8) >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _outputTail = NumberOutput.outputInt(s, _outputBuffer, _outputTail);
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeNumber(int i) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if ((_outputTail + 11) >= _outputEnd) {
            _flushBuffer();
        }
        if (_cfgNumbersAsStrings) {
            _writeQuotedInt(i);
            return;
        }
        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
    }

    private void _writeQuotedInt(int i) throws IOException {
        if ((_outputTail + 13) >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _outputTail = NumberOutput.outputInt(i, _outputBuffer, _outputTail);
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeNumber(long l) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if (_cfgNumbersAsStrings) {
            _writeQuotedLong(l);
            return;
        }
        if ((_outputTail + 21) >= _outputEnd) {
            _flushBuffer();
        }
        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
    }

    private void _writeQuotedLong(long l) throws IOException {
        if ((_outputTail + 23) >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        _outputTail = NumberOutput.outputLong(l, _outputBuffer, _outputTail);
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeNumber(BigInteger value) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if (value == null) {
            _writeNull();
        } else if (_cfgNumbersAsStrings) {
            _writeQuotedRaw(value.toString());
        } else {
            writeRaw(value.toString());
        }
    }

    @SuppressWarnings("deprecation")
    @Override
    public void writeNumber(double d) throws IOException {
        if (_cfgNumbersAsStrings || (NumberOutput.notFinite(d) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
            writeString(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
            return;
        }
        _verifyValueWrite(WRITE_NUMBER);
        writeRaw(NumberOutput.toString(d, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
    }

    @SuppressWarnings("deprecation")
    @Override
    public void writeNumber(float f) throws IOException {
        if (_cfgNumbersAsStrings || (NumberOutput.notFinite(f) && Feature.QUOTE_NON_NUMERIC_NUMBERS.enabledIn(_features))) {
            writeString(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
            return;
        }
        _verifyValueWrite(WRITE_NUMBER);
        writeRaw(NumberOutput.toString(f, isEnabled(Feature.USE_FAST_DOUBLE_WRITER)));
    }

    @Override
    public void writeNumber(BigDecimal value) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if (value == null) {
            _writeNull();
        } else if (_cfgNumbersAsStrings) {
            _writeQuotedRaw(_asString(value));
        } else {
            writeRaw(_asString(value));
        }
    }

    @Override
    public void writeNumber(String encodedValue) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if (encodedValue == null) {
            _writeNull();
        } else if (_cfgNumbersAsStrings) {
            _writeQuotedRaw(encodedValue);
        } else {
            writeRaw(encodedValue);
        }
    }

    @Override
    public void writeNumber(char[] encodedValueBuffer, int offset, int length) throws IOException {
        _verifyValueWrite(WRITE_NUMBER);
        if (_cfgNumbersAsStrings) {
            _writeQuotedRaw(encodedValueBuffer, offset, length);
        } else {
            writeRaw(encodedValueBuffer, offset, length);
        }
    }

    private void _writeQuotedRaw(String value) throws IOException {
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        writeRaw(value);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    private void _writeQuotedRaw(char[] text, int offset, int length) throws IOException {
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
        writeRaw(text, offset, length);
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = _quoteChar;
    }

    @Override
    public void writeBoolean(boolean state) throws IOException {
        _verifyValueWrite(WRITE_BOOLEAN);
        if ((_outputTail + 5) >= _outputEnd) {
            _flushBuffer();
        }
        byte[] keyword = state ? TRUE_BYTES : FALSE_BYTES;
        int len = keyword.length;
        System.arraycopy(keyword, 0, _outputBuffer, _outputTail, len);
        _outputTail += len;
    }

    @Override
    public void writeNull() throws IOException {
        _verifyValueWrite(WRITE_NULL);
        _writeNull();
    }

    @Override
    public void writeEnum(Enum<?> enumValue) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    protected final void _verifyValueWrite(String typeMsg) throws IOException {
        final int status = _writeContext.writeValue();
        if (_cfgPrettyPrinter != null) {
            _verifyPrettyValueWrite(typeMsg, status);
            return;
        }
        byte b;
        switch (status) {
            case JsonWriteContext.STATUS_OK_AS_IS:
            default:
                return;
            case JsonWriteContext.STATUS_OK_AFTER_COMMA:
                b = BYTE_COMMA;
                break;
            case JsonWriteContext.STATUS_OK_AFTER_COLON:
                b = BYTE_COLON;
                break;
            case JsonWriteContext.STATUS_OK_AFTER_SPACE:
                if (_rootValueSeparator != null) {
                    byte[] raw = _rootValueSeparator.asUnquotedUTF8();
                    if (raw.length > 0) {
                        _writeBytes(raw);
                    }
                }
                return;
            case JsonWriteContext.STATUS_EXPECT_NAME:
                _reportCantWriteValueExpectName(typeMsg);
                return;
        }
        if (_outputTail >= _outputEnd) {
            _flushBuffer();
        }
        _outputBuffer[_outputTail++] = b;
    }

    @Override
    public void flush() throws IOException {
        _flushBuffer();
        if (_outputStream != null) {
            if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
                _outputStream.flush();
            }
        }
    }

    @Override
    public void close() throws IOException {
        super.close();
        IOException flushFail = null;
        try {
            if ((_outputBuffer != null) && isEnabled(Feature.AUTO_CLOSE_JSON_CONTENT)) {
                while (true) {
                    JsonStreamContext ctxt = getOutputContext();
                    if (ctxt.inArray()) {
                        writeEndArray();
                    } else if (ctxt.inObject()) {
                        writeEndObject();
                    } else {
                        break;
                    }
                }
            }
            _flushBuffer();
        } catch (IOException e) {
            flushFail = e;
        }
        _outputTail = 0;
        if (_outputStream != null) {
            try {
                if (_ioContext.isResourceManaged() || isEnabled(Feature.AUTO_CLOSE_TARGET)) {
                    _outputStream.close();
                } else if (isEnabled(Feature.FLUSH_PASSED_TO_STREAM)) {
                    _outputStream.flush();
                }
            } catch (IOException | RuntimeException e) {
                if (flushFail != null) {
                    e.addSuppressed(flushFail);
                }
                throw e;
            }
        }

        _releaseBuffers();
        if (flushFail != null) {
            throw flushFail;
        }
    }

    @Override
    protected void _releaseBuffers() {
        byte[] buf = _outputBuffer;
        if (buf != null && _bufferRecyclable) {
            _outputBuffer = null;
            _ioContext.releaseWriteEncodingBuffer(buf);
        }
        char[] cbuf = _charBuffer;
        if (cbuf != null) {
            _charBuffer = null;
            _ioContext.releaseConcatBuffer(cbuf);
        }
    }

    private void _writeBytes(byte[] bytes) throws IOException {
        final int len = bytes.length;
        if ((_outputTail + len) > _outputEnd) {
            _flushBuffer();
            if (len > MAX_BYTES_TO_BUFFER) {
                _outputStream.write(bytes, 0, len);
                return;
            }
        }
        System.arraycopy(bytes, 0, _outputBuffer, _outputTail, len);
        _outputTail += len;
    }

    private void _writeBytes(byte[] bytes, int offset, int len) throws IOException {
        if ((_outputTail + len) > _outputEnd) {
            _flushBuffer();
            if (len > MAX_BYTES_TO_BUFFER) {
                _outputStream.write(bytes, offset, len);
                return;
            }
        }
        System.arraycopy(bytes, offset, _outputBuffer, _outputTail, len);
        _outputTail += len;
    }

    private void _writeStringSegments(String text, boolean addQuotes) throws IOException {
        if (addQuotes) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = _quoteChar;
        }

        int left = text.length();
        int offset = 0;

        while (left > 0) {
            int len = Math.min(_outputMaxContiguous, left);
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(text, offset, len);
            offset += len;
            left -= len;
        }
        if (addQuotes) {
            if (_outputTail >= _outputEnd) {
                _flushBuffer();
            }
            _outputBuffer[_outputTail++] = _quoteChar;
        }
    }

    private void _writeStringSegments(char[] cbuf, int offset, int totalLen) throws IOException {
        do {
            int len = Math.min(_outputMaxContiguous, totalLen);
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(cbuf, offset, len);
            offset += len;
            totalLen -= len;
        } while (totalLen > 0);
    }

    private void _writeStringSegments(String text, int offset, int totalLen) throws IOException {
        do {
            int len = Math.min(_outputMaxContiguous, totalLen);
            if ((_outputTail + len) > _outputEnd) {
                _flushBuffer();
            }
            _writeStringSegment(text, offset, len);
            offset += len;
            totalLen -= len;
        } while (totalLen > 0);
    }

    private void _writeStringSegment(char[] cbuf, int offset, int len) throws IOException {

        len += offset;

        int outputPtr = _outputTail;
        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        while (offset < len) {
            int ch = cbuf[offset];
            if (ch > 0x7F || escCodes[ch] != 0) {
                break;
            }
            outputBuffer[outputPtr++] = (byte) ch;
            ++offset;
        }
        _outputTail = outputPtr;
        if (offset < len) {
            if (_characterEscapes != null) {
                _writeCustomStringSegment2(cbuf, offset, len);
            } else if (_maximumNonEscapedChar == 0) {
                _writeStringSegment2(cbuf, offset, len);
            } else {
                _writeStringSegmentASCII2(cbuf, offset, len);
            }
        }
    }

    private void _writeStringSegment(String text, int offset, int len) throws IOException {
        len += offset;
        int outputPtr = _outputTail;
        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        while (offset < len) {
            int ch = text.charAt(offset);
            if (ch > 0x7F || escCodes[ch] != 0) {
                break;
            }
            outputBuffer[outputPtr++] = (byte) ch;
            ++offset;
        }
        _outputTail = outputPtr;
        if (offset < len) {
            if (_characterEscapes != null) {
                _writeCustomStringSegment2(text, offset, len);
            } else if (_maximumNonEscapedChar == 0) {
                _writeStringSegment2(text, offset, len);
            } else {
                _writeStringSegmentASCII2(text, offset, len);
            }
        }
    }

    private void _writeStringSegment2(final char[] cbuf, int offset, final int end) throws IOException {
        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }

        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        while (offset < end) {
            int ch = cbuf[offset++];
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else {
                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private void _writeStringSegment2(final String text, int offset, final int end) throws IOException {
        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }

        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        while (offset < end) {
            int ch = text.charAt(offset++);
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else {
                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private void _writeStringSegmentASCII2(final char[] cbuf, int offset, final int end) throws IOException {
        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }
        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;
        final int maxUnescaped = _maximumNonEscapedChar;

        while (offset < end) {
            int ch = cbuf[offset++];
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else {
                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch > maxUnescaped) {
                outputPtr = _writeGenericEscape(ch, outputPtr);
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private void _writeStringSegmentASCII2(final String text, int offset, final int end) throws IOException {
        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }

        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;
        final int maxUnescaped = _maximumNonEscapedChar;

        while (offset < end) {
            int ch = text.charAt(offset++);
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else {
                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch > maxUnescaped) {
                outputPtr = _writeGenericEscape(ch, outputPtr);
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private void _writeCustomStringSegment2(final char[] cbuf, int offset, final int end) throws IOException {

        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }
        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        final int maxUnescaped = (_maximumNonEscapedChar <= 0) ? 0xFFFF : _maximumNonEscapedChar;
        final CharacterEscapes customEscapes = _characterEscapes;

        while (offset < end) {
            int ch = cbuf[offset++];
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else if (escape == CharacterEscapes.ESCAPE_CUSTOM) {
                    SerializableString esc = customEscapes.getEscapeSequence(ch);
                    if (esc == null) {
                        _reportError("Invalid custom escape definitions; custom escape not found for character code 0x" + Integer.toHexString(ch) + ", although was supposed to have one");
                    }
                    outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end - offset);
                } else {

                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch > maxUnescaped) {
                outputPtr = _writeGenericEscape(ch, outputPtr);
                continue;
            }
            SerializableString esc = customEscapes.getEscapeSequence(ch);
            if (esc != null) {
                outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end - offset);
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private void _writeCustomStringSegment2(final String text, int offset, final int end) throws IOException {
        if ((_outputTail + 6 * (end - offset)) > _outputEnd) {
            _flushBuffer();
        }
        int outputPtr = _outputTail;

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;

        final int maxUnescaped = (_maximumNonEscapedChar <= 0) ? 0xFFFF : _maximumNonEscapedChar;
        final CharacterEscapes customEscapes = _characterEscapes;

        while (offset < end) {
            int ch = text.charAt(offset++);
            if (ch <= 0x7F) {
                if (escCodes[ch] == 0) {
                    outputBuffer[outputPtr++] = (byte) ch;
                    continue;
                }
                int escape = escCodes[ch];
                if (escape > 0) {
                    outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                    outputBuffer[outputPtr++] = (byte) escape;
                } else if (escape == CharacterEscapes.ESCAPE_CUSTOM) {
                    SerializableString esc = customEscapes.getEscapeSequence(ch);
                    if (esc == null) {
                        _reportError("Invalid custom escape definitions; custom escape not found for character code 0x" + Integer.toHexString(ch) + ", although was supposed to have one");
                    }
                    outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end - offset);
                } else {
                    outputPtr = _writeGenericEscape(ch, outputPtr);
                }
                continue;
            }
            if (ch > maxUnescaped) {
                outputPtr = _writeGenericEscape(ch, outputPtr);
                continue;
            }
            SerializableString esc = customEscapes.getEscapeSequence(ch);
            if (esc != null) {
                outputPtr = _writeCustomEscape(outputBuffer, outputPtr, esc, end - offset);
                continue;
            }
            if (ch <= 0x7FF) {
                outputBuffer[outputPtr++] = (byte) (0xc0 | (ch >> 6));
                outputBuffer[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
            } else {
                outputPtr = _outputMultiByteChar(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    private int _writeCustomEscape(byte[] outputBuffer, int outputPtr, SerializableString esc, int remainingChars) throws IOException {
        byte[] raw = esc.asUnquotedUTF8();
        int len = raw.length;
        if (len > 6) {
            return _handleLongCustomEscape(outputBuffer, outputPtr, _outputEnd, raw, remainingChars);
        }

        System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
        return (outputPtr + len);
    }

    private int _handleLongCustomEscape(byte[] outputBuffer, int outputPtr, int outputEnd, byte[] raw, int remainingChars) throws IOException {
        final int len = raw.length;
        if ((outputPtr + len) > outputEnd) {
            _outputTail = outputPtr;
            _flushBuffer();
            outputPtr = _outputTail;
            if (len > outputBuffer.length) {
                _outputStream.write(raw, 0, len);
                return outputPtr;
            }
        }
        System.arraycopy(raw, 0, outputBuffer, outputPtr, len);
        outputPtr += len;

        if ((outputPtr + 6 * remainingChars) > outputEnd) {
            _outputTail = outputPtr;
            _flushBuffer();
            return _outputTail;
        }
        return outputPtr;
    }

    private void _writeUTF8Segments(byte[] utf8, int offset, int totalLen) throws IOException {
        do {
            int len = Math.min(_outputMaxContiguous, totalLen);
            _writeUTF8Segment(utf8, offset, len);
            offset += len;
            totalLen -= len;
        } while (totalLen > 0);
    }

    private void _writeUTF8Segment(byte[] utf8, final int offset, final int len) throws IOException {
        final int[] escCodes = _outputEscapes;

        for (int ptr = offset, end = offset + len; ptr < end; ) {
            int ch = utf8[ptr++];
            if ((ch >= 0) && escCodes[ch] != 0) {
                _writeUTF8Segment2(utf8, offset, len);
                return;
            }
        }

        if ((_outputTail + len) > _outputEnd) {
            _flushBuffer();
        }
        System.arraycopy(utf8, offset, _outputBuffer, _outputTail, len);
        _outputTail += len;
    }

    private void _writeUTF8Segment2(final byte[] utf8, int offset, int len) throws IOException {
        int outputPtr = _outputTail;

        if ((outputPtr + (len * 6)) > _outputEnd) {
            _flushBuffer();
            outputPtr = _outputTail;
        }

        final byte[] outputBuffer = _outputBuffer;
        final int[] escCodes = _outputEscapes;
        len += offset;

        while (offset < len) {
            byte b = utf8[offset++];
            int ch = b;
            if (ch < 0 || escCodes[ch] == 0) {
                outputBuffer[outputPtr++] = b;
                continue;
            }
            int escape = escCodes[ch];
            if (escape > 0) {
                outputBuffer[outputPtr++] = BYTE_BACKSLASH;
                outputBuffer[outputPtr++] = (byte) escape;
            } else {
                outputPtr = _writeGenericEscape(ch, outputPtr);
            }
        }
        _outputTail = outputPtr;
    }

    protected final void _writeBinary(Base64Variant b64variant, byte[] input, int inputPtr, final int inputEnd) throws IOException {

        int safeInputEnd = inputEnd - 3;

        int safeOutputEnd = _outputEnd - 6;
        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;

        while (inputPtr <= safeInputEnd) {
            if (_outputTail > safeOutputEnd) {
                _flushBuffer();
            }
            int b24 = (input[inputPtr++]) << 8;
            b24 |= (input[inputPtr++]) & 0xFF;
            b24 = (b24 << 8) | ((input[inputPtr++]) & 0xFF);
            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
            if (--chunksBeforeLF <= 0) {
                _outputBuffer[_outputTail++] = '\\';
                _outputBuffer[_outputTail++] = 'n';
                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
            }
        }

        int inputLeft = inputEnd - inputPtr;
        if (inputLeft > 0) {
            if (_outputTail > safeOutputEnd) {
                _flushBuffer();
            }
            int b24 = (input[inputPtr++]) << 16;
            if (inputLeft == 2) {
                b24 |= ((input[inputPtr++]) & 0xFF) << 8;
            }
            _outputTail = b64variant.encodeBase64Partial(b24, inputLeft, _outputBuffer, _outputTail);
        }
    }

    protected final int _writeBinary(Base64Variant b64variant, InputStream data, byte[] readBuffer, int bytesLeft) throws IOException {
        int inputPtr = 0;
        int inputEnd = 0;
        int lastFullOffset = -3;

        int safeOutputEnd = _outputEnd - 6;
        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;

        while (bytesLeft > 2) {
            if (inputPtr > lastFullOffset) {
                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
                inputPtr = 0;
                if (inputEnd < 3) {
                    break;
                }
                lastFullOffset = inputEnd - 3;
            }
            if (_outputTail > safeOutputEnd) {
                _flushBuffer();
            }
            int b24 = (readBuffer[inputPtr++]) << 8;
            b24 |= (readBuffer[inputPtr++]) & 0xFF;
            b24 = (b24 << 8) | ((readBuffer[inputPtr++]) & 0xFF);
            bytesLeft -= 3;
            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
            if (--chunksBeforeLF <= 0) {
                _outputBuffer[_outputTail++] = '\\';
                _outputBuffer[_outputTail++] = 'n';
                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
            }
        }

        if (bytesLeft > 0) {
            inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, bytesLeft);
            inputPtr = 0;
            if (inputEnd > 0) {
                if (_outputTail > safeOutputEnd) {
                    _flushBuffer();
                }
                int b24 = (readBuffer[inputPtr++]) << 16;
                int amount;
                if (inputPtr < inputEnd) {
                    b24 |= ((readBuffer[inputPtr]) & 0xFF) << 8;
                    amount = 2;
                } else {
                    amount = 1;
                }
                _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
                bytesLeft -= amount;
            }
        }
        return bytesLeft;
    }

    protected final int _writeBinary(Base64Variant b64variant, InputStream data, byte[] readBuffer) throws IOException {
        int inputPtr = 0;
        int inputEnd = 0;
        int lastFullOffset = -3;
        int bytesDone = 0;

        int safeOutputEnd = _outputEnd - 6;
        int chunksBeforeLF = b64variant.getMaxLineLength() >> 2;

        while (true) {
            if (inputPtr > lastFullOffset) {
                inputEnd = _readMore(data, readBuffer, inputPtr, inputEnd, readBuffer.length);
                inputPtr = 0;
                if (inputEnd < 3) {
                    break;
                }
                lastFullOffset = inputEnd - 3;
            }
            if (_outputTail > safeOutputEnd) {
                _flushBuffer();
            }

            int b24 = (readBuffer[inputPtr++]) << 8;
            b24 |= (readBuffer[inputPtr++]) & 0xFF;
            b24 = (b24 << 8) | ((readBuffer[inputPtr++]) & 0xFF);
            bytesDone += 3;
            _outputTail = b64variant.encodeBase64Chunk(b24, _outputBuffer, _outputTail);
            if (--chunksBeforeLF <= 0) {
                _outputBuffer[_outputTail++] = '\\';
                _outputBuffer[_outputTail++] = 'n';
                chunksBeforeLF = b64variant.getMaxLineLength() >> 2;
            }
        }

        if (inputPtr < inputEnd) {
            if (_outputTail > safeOutputEnd) {
                _flushBuffer();
            }
            int b24 = (readBuffer[inputPtr++]) << 16;
            int amount = 1;
            if (inputPtr < inputEnd) {
                b24 |= ((readBuffer[inputPtr]) & 0xFF) << 8;
                amount = 2;
            }
            bytesDone += amount;
            _outputTail = b64variant.encodeBase64Partial(b24, amount, _outputBuffer, _outputTail);
        }
        return bytesDone;
    }

    private int _readMore(InputStream in, byte[] readBuffer, int inputPtr, int inputEnd, int maxRead) throws IOException {
        int i = 0;
        while (inputPtr < inputEnd) {
            readBuffer[i++] = readBuffer[inputPtr++];
        }
        inputPtr = 0;
        inputEnd = i;
        maxRead = Math.min(maxRead, readBuffer.length);

        do {
            int length = maxRead - inputEnd;
            if (length == 0) {
                break;
            }
            int count = in.read(readBuffer, inputEnd, length);
            if (count < 0) {
                return inputEnd;
            }
            inputEnd += count;
        } while (inputEnd < 3);
        return inputEnd;
    }

    private int _outputRawMultiByteChar(int ch, char[] cbuf, int inputOffset, int inputEnd) throws IOException {
        if (ch >= SURR1_FIRST) {
            if (ch <= SURR2_LAST) {
                if (inputOffset >= inputEnd || cbuf == null) {
                    _reportError(String.format("Split surrogate on writeRaw() input (last character): first character 0x%4x", ch));
                } else {
                    _outputSurrogates(ch, cbuf[inputOffset]);
                }
                return inputOffset + 1;
            }
        }
        final byte[] bbuf = _outputBuffer;
        bbuf[_outputTail++] = (byte) (0xe0 | (ch >> 12));
        bbuf[_outputTail++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
        bbuf[_outputTail++] = (byte) (0x80 | (ch & 0x3f));
        return inputOffset;
    }

    protected final void _outputSurrogates(int surr1, int surr2) throws IOException {
        int c = _decodeSurrogate(surr1, surr2);
        if ((_outputTail + 4) > _outputEnd) {
            _flushBuffer();
        }
        final byte[] bbuf = _outputBuffer;
        bbuf[_outputTail++] = (byte) (0xf0 | (c >> 18));
        bbuf[_outputTail++] = (byte) (0x80 | ((c >> 12) & 0x3f));
        bbuf[_outputTail++] = (byte) (0x80 | ((c >> 6) & 0x3f));
        bbuf[_outputTail++] = (byte) (0x80 | (c & 0x3f));
    }

    private int _outputMultiByteChar(int ch, int outputPtr) {
        byte[] HEX_CHARS = getHexBytes();
        byte[] bbuf = _outputBuffer;
        if (ch >= SURR1_FIRST && ch <= SURR2_LAST) {

            bbuf[outputPtr++] = BYTE_BACKSLASH;
            bbuf[outputPtr++] = BYTE_u;
            bbuf[outputPtr++] = HEX_CHARS[(ch >> 12) & 0xF];
            bbuf[outputPtr++] = HEX_CHARS[(ch >> 8) & 0xF];
            bbuf[outputPtr++] = HEX_CHARS[(ch >> 4) & 0xF];
            bbuf[outputPtr++] = HEX_CHARS[ch & 0xF];
        } else {
            bbuf[outputPtr++] = (byte) (0xe0 | (ch >> 12));
            bbuf[outputPtr++] = (byte) (0x80 | ((ch >> 6) & 0x3f));
            bbuf[outputPtr++] = (byte) (0x80 | (ch & 0x3f));
        }
        return outputPtr;
    }

    private void _writeNull() throws IOException {
        if ((_outputTail + 4) >= _outputEnd) {
            _flushBuffer();
        }
        System.arraycopy(NULL_BYTES, 0, _outputBuffer, _outputTail, 4);
        _outputTail += 4;
    }

    private int _writeGenericEscape(int charToEscape, int outputPtr) {
        final byte[] bbuf = _outputBuffer;
        byte[] HEX_CHARS = getHexBytes();
        bbuf[outputPtr++] = BYTE_BACKSLASH;
        bbuf[outputPtr++] = BYTE_u;
        if (charToEscape > 0xFF) {
            int hi = (charToEscape >> 8) & 0xFF;
            bbuf[outputPtr++] = HEX_CHARS[hi >> 4];
            bbuf[outputPtr++] = HEX_CHARS[hi & 0xF];
            charToEscape &= 0xFF;
        } else {
            bbuf[outputPtr++] = BYTE_0;
            bbuf[outputPtr++] = BYTE_0;
        }

        bbuf[outputPtr++] = HEX_CHARS[charToEscape >> 4];
        bbuf[outputPtr++] = HEX_CHARS[charToEscape & 0xF];
        return outputPtr;
    }

    protected final void _flushBuffer() throws IOException {
        int len = _outputTail;
        if (len > 0) {
            _outputTail = 0;
            _outputStream.write(_outputBuffer, 0, len);
        }
    }

    private byte[] getHexBytes() {
        return _cfgWriteHexUppercase ? HEX_BYTES_UPPER : HEX_BYTES_LOWER;
    }
}

